<?php
declare (strict_types = 1);

namespace util;

use Exception;
use ReflectionClass;
class ClassAttrParser {

    protected $reflection;
    private $metaAttrs = null;

    public static function parser($objOrName) {
        return new self($objOrName);
    }

    public function __construct($objOrName) {
        $this->reflection = new ReflectionClass($objOrName);
    }

    public function getMetaAttr($name, $default = null) {
        $attrs = $this->parseMetaAttrs();
        if(isset($attrs[$name])) {
            if(isset($attrs[$name]['VALUE'])) return $attrs[$name]['VALUE'];
            return $attrs[$name];
        }
        return $default;
    }

    public function parseMeta() {
        $doc = $this->reflection->getDocComment();
        $result = $this->parseObjDocs($doc);
        return $result;
    }

    public function parseMetaAttrs() {
        if($this->metaAttrs == null) {
            $metas = $this->parseMeta();
            $result = [];
            foreach ($metas as $meta) {
                if(isset($result[$meta['ATTR']])) {
                    $old = $result[$meta['ATTR']];
                    if(isset($old['ATTR'])) {
                        $result[$meta['ATTR']] = [];
                        $result[$meta['ATTR']][] = $old;
                        $result[$meta['ATTR']][] = $meta;
                    } else {
                        $result[$meta['ATTR']][] = $meta;
                    }
                } else {
                    $result[$meta['ATTR']] = $meta;
                }
            }
            $this->metaAttrs = $result;
        }
        return $this->metaAttrs;
    }

    public function parseMethodAttrs($name) {
        $result = [];
        if(!$this->reflection->hasMethod($name)) return $result;
        $refMethod = $this->reflection->getMethod($name);
        $doc = $refMethod->getDocComment();
        // 一个属性上可能有多个注解
        $objs = $this->parseObjDocs($doc);
        foreach ($objs as &$obj) {
            $result[$obj['attr']] = $obj;
        }
        return $result;
    }

    public function parseProperties() {
        $result = [];
        foreach ($this->reflection->getProperties() as $attrRef) {
            $name = $attrRef->name;
            // var_dump($attrRef);
            // var_dump($name);
            $doc = $attrRef->getDocComment();
            $result[$name] = $this->parseObjDocs($doc);
        }
        return $result;
    }

    public function parseStaticProperties() {
        $result = [];
        // var_dump($this->reflection->getStaticProperties());
        foreach($this->reflection->getStaticProperties() as $attrRef) {
            // var_dump($attrRef);
        }
        return $result;
    }

    public function parseConstants() {
        $result = [];
        foreach($this->reflection->getReflectionConstants() as $attrRef) {
            $name = $attrRef->name;
            $doc = $attrRef->getDocComment();
            // 一个属性上可能有多个注解
            $objs = $this->parseObjDocs($doc);
            foreach ($objs as &$obj) {
                $obj['name'] = $name;
                $obj['value'] = $attrRef->getValue();
            }
            $result[] = $objs;
        }
        return $result;
    }

    /**
     * 处理函数类型
     */
    private function parseObjDocs($doc) {
        $lines = $this->splicCommentLines($doc);
        $result = [];
        foreach ($lines as $line) {
            if($line[0] != '@') continue;
            $attr = trim(mb_substr($line, 1));
            // 匹配 Column(['name' => 'username', 'comment' => '用户名' ])
            $tokens = [];
            preg_match("/(\w+)(\((.*)\))?/", $attr, $tokens);
            if(count($tokens) == 4 && $tokens[2][0] == '(') {
                $objName = $tokens[1];
                $objParams = $tokens[3];
                try {
                    $str = '$objParams = '.$objParams .';';
                    eval($str);
                } catch (\Throwable $th) {
                    throw new Exception("注解参数有误: ".$attr);
                }
                $objParams['ATTR'] = $objName;
                $result[] = $objParams;
                continue;
            }
            $tokens = explode(' ', $attr);
            // 单个注解
            if(count($tokens) == 1) {
                $result[] = [
                    'ATTR' => $tokens[1]
                ];
                continue;
            } 
            // KEY VALUE
            else if(count($tokens) == 2) {
                $result[] = [
                    'ATTR' => $tokens[0],
                    'VALUE' => $tokens[1],
                ];
            } else {
                throw new Exception("未知注解属性: ". $line);
            }
        }
        return $result;
    }

    private function splicCommentLines($doc) {
        if(!$doc) return [];
        $lines = explode("\n", $doc);
        $result = [];
        foreach ($lines as $line) {
            $line = trim($line);
            if(in_array($line, ['/**', '*/', '', '*'])) continue;
            if($line[0] == '*') $line = trim(mb_substr($line, 1));
            $result[] = $line;
        }
        return $result;
    }
}